#include "client.hpp"

#include <boost/bind.hpp>
#include <iostream>
#include <boost/foreach.hpp>

#include <standard.hpp>
#include "web_socket_server.hpp"
#include "handshake_handler.hpp"
#include "utf8-cpp/utf8.h"

client::client(boost::asio::io_service& io_service, web_socket_server* server, boost::shared_ptr<logger> log, uint64_t index) :
    _socket(io_service),
    _server(server),
    _logger(log),
    _index(index),
    _receiver(server->receiver().get())
{
}

void client::start()
{
    boost::asio::async_read(_socket, boost::asio::buffer(_buf, _buf.size()), boost::asio::transfer_at_least(1),
                            boost::bind(&client::handle_shake, shared_from_this(), boost::asio::placeholders::error,
                            boost::asio::placeholders::bytes_transferred));
}

void client::send(const std::wstring& message)
{
    //std::cout << "sending message: " << (int)message[0] << message << std::endl << "to: " << _socket.remote_endpoint().address().to_string() << std::endl;

    /*int wrapped_message_size = message.size() + 2;
    unsigned char wrapped_message[wrapped_message_size];
    wrapped_message[0] = 0;
    memcpy(wrapped_message + 1, message.c_str(), wrapped_message_size - 2);
    wrapped_message[wrapped_message_size - 1] = 255;*/

    std::string utf8_msg;
    utf8::utf32to8(message.begin(), message.end(), std::back_inserter(utf8_msg));

    unsigned char masked_message[utf8_msg.size() + 2];
    masked_message[0] = 0x81;
    masked_message[1] = utf8_msg.size() > 126 ? 126 : utf8_msg.size();
    //std::cout << "derp2: " << static_cast<int>(masked_message[1] & 0x7f) << std::endl;
    memcpy(masked_message + 2, utf8_msg.c_str(), utf8_msg.size());

    boost::asio::async_write(_socket, boost::asio::buffer(masked_message, utf8_msg.size() + 2), boost::asio::transfer_all(),
                             boost::bind(&client::handle_write, shared_from_this(),
                             boost::asio::placeholders::error,
                             boost::asio::placeholders::bytes_transferred));
}

void client::handle_write(const boost::system::error_code& error, size_t UNUSED(bytes_transferred))
{
    if(error)
    {
        std::cout << "Error sending data" << std::endl;
    }

    /*std::stringstream ss;
    ss << bytes_transferred;
    std::cout << "transferred " << ss.str() << " bytes to: " << _socket.remote_endpoint().address().to_string() << std::endl;*/
}

void client::handle_shake(const boost::system::error_code& UNUSED(error), std::size_t bytes_transferred)
{
    _message = std::string(_buf.begin(), bytes_transferred);
    std::cout << _message << std::endl;

    std::string final_output = _server->handler().handle_shake(_message);
    std::cout << final_output << std::endl;

    boost::asio::async_write(_socket, boost::asio::buffer(final_output), boost::asio::transfer_all(),
                             boost::bind(&client::handle_write, shared_from_this(),
                             boost::asio::placeholders::error,
                             boost::asio::placeholders::bytes_transferred));

    boost::asio::async_read(_socket, boost::asio::buffer(_buf, _buf.size()), boost::asio::transfer_at_least(1),
                            boost::bind(&client::handle_read, shared_from_this(), _1, _2));
}

void client::handle_read(const boost::system::error_code& error, std::size_t bytes_transferred)
{
    if(!error)
    {
        _message = std::string(_buf.begin(), bytes_transferred);
        std::cout << "message size: " << _message.size() << std::endl;

        //unsigned int continuation = _message.c_str()[0] & 0x7f;
        unsigned int length = _message.c_str()[1] & 0x7f;
        std::string mask;

        while(_message.size() > 0)
        {
            std::cout << "derp: " << std::endl;
            for(unsigned int i = 0; i < _message.size(); i++)
            {
                std::cout << static_cast<int>(_message.c_str()[i] & 0x7f) << ", ";
            }
            std::cout << std::endl;

            if (length < 126)
            {
                mask = _message.substr(2, 4);
                _message = _message.substr(6);
            }
            else if (length == 126)
            {
                mask = _message.substr(4, 4);
                _message = _message.substr(8);
                length = _message.size();
            }

            std::string unmasked_message(length, ' ');
            for(unsigned int i = 0; i < length; i++)
            {
                unmasked_message[i] = _message[i] ^ mask[i%4];
            }

            std::wstring message;
            utf8::utf8to32(unmasked_message.begin(), unmasked_message.end(), std::back_inserter(message));
            std::cout << "message received from: " << _socket.remote_endpoint().address().to_string() << std::endl;
            BOOST_FOREACH(wchar_t c, message)
            {
                std::cout << static_cast<int>(c) << ", ";
            }
            std::cout << std::endl;

            _receiver->receive_message(message, shared_from_this());

            //firefox disconnect message
            if(length == 2 && static_cast<int>(_message[0]) == 3 && static_cast<int>(_message[1]) == -24)
            {
                disconnect();
                return;
            }
            _message = _message.substr(length);
        }

        boost::asio::async_read(_socket, boost::asio::buffer(_buf, _buf.size()), boost::asio::transfer_at_least(1),
                                boost::bind(&client::handle_read, shared_from_this(), _1, _2));
    }
    else
    {
        _logger->log(logger::error, error.message());
        disconnect();
    }
}

void client::disconnect()
{
    _logger->log(logger::info, _socket.remote_endpoint().address().to_string() + std::string(" disconnected."));
    _disconnected(shared_from_this());
    _server->clients().erase(_index);
}
